# --------------------------------------------------------------------------------- #
# NAMSPACE DIFF TOOL (NDT) wxPython IMPLEMENTATION
# Inspired by an idea from Robin Dunn.
#
# Andrea Gavana, @ 09 Nov 2011
# Latest revision: 15 Nov 2011, 16.00 GMT
#
#
# TODO list:
#
# 1)  Smarter handling of the left tree control, the current approach is a bit fragile;
# 2)  The left tree control really needs to be heavily virtual, but none of the
#     tree controls in wxPython offer this functionality. DataViewCtrl may be a
#     possible candidate but that would mean abandon wxPython 2.8 (and learn how to
#     use DataViewCtrl...);
# 3)  Allow to export the diff between two libraries into a XML file or similar;
# 4)  Implement smarter tree filters, possibly showing functions only, classes only,
#     attributes only and a combination of these flags;
# 5)  Allow the user to scan more than one library at a time (maybe using a custom
#     wx.GenericDirCtrl with checkboxes to select which libraries to scan and then
#     run the scans in parallel (or serially one after the other);
# 6)  Find an alternative to wx.html, as on other platforms the results of the "Compare"
#     process will look a bit crappy;
# 7)  Provide an intelligent context menu for the tree control, for example to jump from
#     an "inherited" attribute to the real one, or to examine the source code of an
#     object;
# 8)  Extend the NDT documentation on the usage and capabilities;
# 9)  Add docstrings for all the methods in the GUI implementation.
#
#
# For all kind of problems, requests of enhancements and bug reports, please
# write to me at:
#
# andrea.gavana@gmail.com
# andrea.gavana@maerskoil.com
#
# Or, obviously, to the wxPython mailing list!!!
#
#
# End of comments
# --------------------------------------------------------------------------------- #


"""
Description
===========

The `Namespace Diff Tool` (NDT) is a graphical user interface that can be used to
discover differences between different versions of a library, or even between
different iterations/sub-versions of the same library.

The tool can be used to identify what is missing and still needs to be implemented,
or what is new in a new release, which items do not have docstrings and so on. More
detailed explanation is given in the sections below.

Full description of the original idea by Robin Dunn:

http://svn.wxwidgets.org/viewvc/wx/wxPython/Phoenix/trunk/TODO.txt?view=markup


:warning: As most of the widgets in the GUI are owner drawn or custom, it is highly
 probable that the interface itself will look messy on other platforms (Mac, I am
 talking to you). Please do try and create a patch to fix any possible issue in this
 sense.


Requirements
============

In order to run NDT, these packages need to be installed:

- Python 2.X (where 5 <= X <= 7);
- wxPython >= 2.8.10;
- SQLAlchemy >= 0.6.4.


NDT Capabilities
================

NDT is essentially able to execute 3 different main actions depending on the
user choice. The panel at the top of the GUI offers the choices described
below.


Scan
----

By clicking the "Browse" button in the top panel and selecting a Python
`__init__.py` file (which represents the `__init__.py` file for a
library/package) and clicking on the `Start` button on the bottom right of the GUI
(or hitting the ``F5`` key) will start the scan process for the selected library
or package.

This action is executed in a separate Python process in order to not interfere
with the main GUI and to not pollute the GUI namespace. The scan process can be
run with a different Python version than the one used to run the GUI: you can
choose your default Python version from the "Options" menu.

During the scan phase, the tool does live-objects introspection coupled with raw
Python files source code analysis. The tool records the following information
depending on the object type:

* Packages/Modules: module name, filename, docstrings and number of submodules,
  classes, methods/functions, properties and attributes (i.e., top-level "constants"
  in a module), inheritance diagrams for all the classes contained in the module
  (for all modules where the number of classes is less than 20);
* Classes: class name, LOC, subclasses, superclasses, class tree, docstrings, class
  signature and number of methods/functions, properties, attributes and inheritance
  diagrams;
* Methods/Functions: name, type (i.e., classmethod, instancemethod, staticmethod,
  builtin method, builtin function, method descriptor), first LOC where the method
  is implemented, LOC, parameters name and default values (if any), method signature
  and docstrings;
* Properties: property name, getter (fget), setter (fset), deleter (fdel) and
  docstrings;
* Attributes: attribute name, kind (i.e., BoolType, IntType, ListType and so on),
  its value and possibly docstrings.

Once the scan is finished, the resulting recorded information is stored in a
database (using SQLAlchemy) in the user's data directory; in my case, on my
workplace machine the database is located in:

``C:\Users\AGA108\AppData\Roaming\NDT\NDT_Database.db``

The user's preferences are stored in the same folder.

NDT also stores a list of recent scanned files and a list of recent actions (i.e.,
`Scan`, `Load` or `Compare`) under the "File" menu.

:note: Inheritance diagrams can be visualized in the NDT interface for classes and
 modules as well (as long as the number of classes is less than 20 in any give
 module). However, this requires the GraphViz application:

 http://www.graphviz.org/

 To be installed and on visible on the path (i.e., the `dot` command must be executable
 by NDT).


Load
----

Once at least one scan has been successfully performed, you can view the results
of this scan by loading a single instance from the NDT database.

By selecting one item in the dropdown combobox labelled as "Library name & version"
and clicking on the `Start` button on the bottom right of the GUI (or hitting the
``F5`` key), the records for that library are retrieved from the database and
initially shown as a tree on the left side of the GUI.

The tree stores information about packages, modules, classes and so on in a
hierarchical way, so you can drill down the tree structure and analyze the content
of a certain item.

If you are looking for a specific method/class/etc..., you can filter the tree by
typing part of the object's name on the bottom left of the GUI and hit the ``Enter``
key to see all the items with a (partly) matching name. This tree search is
**case insensitive**.

By selecting one of the items in the tree, a new page will be created in the central
part of the GUI showing different information about the selected item (refer to the
`Scan` section above for more information about what gets displayed).

From the "Options" menu, If you activate the option of showing "inherited" attributes
(i.e., attributes that live in a namespace but for which the implementation is
actually in another module: for example `wx.TextCtrl` lives in the `wx` namespace
but its implementation is in `wx._controls.TextCtrl`), then these "inherited" objects
will be represented in the tree with a slanted font and a light grey colour.

:note: For rendering speed considerations, it is not recommended to turn on the "Show
inherited items" option menu.


Compare
-------

If you have previously scanned two (or more) versions of the same library (for
example, wxPython 2.8.12 and 2.9.2.4), you can compare the two versions by selecting
the "Library 1 (base)" and "Library 2" in the comboboxes at the top of the GUI.
Then, by clicking on the `Start` button on the bottom right of the GUI (or hitting
the ``F5`` key), the records for both libraries are retrieved from the database
and shown again in the tree on the left.

In this case, however, the tree items may have different background/foreground
colours depending on the most important difference between the implementation of
that object in library 1 and library 2. A window representing a legend should
automatically pop-up to facilitate the interpretation of these colours, but this
is the colour-coding specification:

- No difference: default text colour (black on Windows) and no background colour;
- Item type difference: default text colour and green background colour;
- Signature difference: blue text colour, no background colour;
- Class tree/subclasses/superclasses difference: red text colour and no background
  colour;
- Get/Set difference (for properties): default text colour and yellow background
  colour;
- An object exists only in library 1: default text colour and orange background
  colour;
- An object exists only in library 2: default text colour and reddish background
  colour;
- Value difference: magenta text colour, no background colour;
- Number of LOC is different: light blue text colour, no background colour;
- First line of code is different: purple text colour, no background colour;
- Docstrings are different: default text colour, grey background colour.

Selecting an item for which no differences were detected will open the same window
in the center of the GUI as described in the `Scan` section, with information
grabbed from library 1.

Selecting an item for which differences were detected will open a HTML window in
the center of the GUI showing a "diff" between the two implementations.

At the moment this looks good only on Windows (using `wx.lib.iewin_old`) because
`wx.html` can not handle the CSS code generated by `difflib`, the standard library
used to compare two objects. 


TODOs
=====

1)  Smarter handling of the left tree control, the current approach is a bit fragile;
2)  The left tree control really needs to be heavily virtual, but none of the
    tree controls in wxPython offer this functionality. `DataViewCtrl` may be a
    possible candidate but that would mean abandon wxPython 2.8 (and learn how to
    use `DataViewCtrl`...);
3)  Allow to export the diff between two libraries into a XML file or similar;
4)  Implement smarter tree filters, possibly showing functions only, classes only,
    attributes only and a combination of these flags;
5)  Allow the user to scan more than one library at a time (maybe using a custom
    `wx.GenericDirCtrl` with checkboxes to select which libraries to scan and then
    run the scans in parallel (or serially one after the other);
6)  Find an alternative to `wx.html`, as on other platforms the results of the
    "Compare" process will look a bit crappy;
7)  Provide an intelligent context menu for the tree control, for example to jump from
    an "inherited" attribute to the real one, or to examine the source code of an
    object;
8)  Extend the NDT documentation on the usage and capabilities;
9)  Add docstrings for all the methods in the GUI implementation.


Supported Platforms
===================

NDT has been tested on the following platforms:
  * Windows (Vista/7);


NDT has been used to scan and analyze the following libraries:

apptools-4.0.0, Bio-1.57, cloud-2.2.4, coverage-3.5, Crypto-2.3, Cython-0.14.1,
dateutil-1.5, docutils-0.7, fwrap-1.0, html5lib-0.90, IPython-0.11, jinja2-2.5.5,
lxml-2.0, matplotlib-1.0.1, mayavi-4.0.0, networkx-1.5, numpy-1.6.1, OpenSSL-0.12,
paramiko-1.7.7.1, PySide-1.0.5, pytz-2011g, reportlab-3788, scipy-0.9.0,
setuptools-0.6, sphinx-1.0.7, sqlalchemy-0.7.1, sympy-0.7.1, traits-4.0.0,
vtk-5.0, wx-2.8.10.1, wx-2.8.12.1, wx-2.9.2.4, xlrd-0.7.1, xlwt-0.7.1, zope-1.0


License And Version
===================

NDT is distributed under the wxPython license.

Latest revision: Andrea Gavana @ 15 Nov 2011, 16.00 GMT

Version 0.1.4

"""


import wx
import os
import sys
import webbrowser
import subprocess

import wx.lib.agw.genericmessagedialog as GMD

import extern.pybusyinfo as PBI
import extern.aui as aui

from dbtree import DBPanelTree
from messagewindow import MessageWindow
from inputpanel import InputPanel
from database import Database
from notebookpage import NotebookPage
from widgets import ExceptionHook, VersionInfo, LibraryFrame, LegendPanel, IgnoreDialog
from threads import ImportProcess
from utilities import now, NDTMode
from librarydescription import GetTopLevelParent

from constants import ID_FirstPerspective, ID_Modern, ID_Classic, ID_Top, ID_Bottom
from constants import ID_NB_Default, ID_NB_FF2, ID_NB_VC71, ID_NB_VC8, ID_NB_Chrome
from constants import ID_InputPane, ID_MessageWindow, ID_DBTree
from constants import ID_CloseTab, ID_CloseAllTabs, ID_CloseAllSubTabs, ID_ShowInherited
from constants import ID_HighlightDocs, ID_ShowLegend, ID_TreeTips, ID_GreyInherited
from constants import ID_IgnoreList

from constants import SCAN, LOAD, COMPARE

from constants import _globalIcons, _knownPackages, _popupMenuItems, _pklFile, _actionIds

from constants import __version__, __locs__

from allicons import catalog


class NDT(wx.Frame):

    def __init__(self):

        wx.Frame.__init__(self, None, -1, u"Namespace Diff Tool")

        self.process = None
        
        self._mgr = aui.AuiManager()
        self._mgr.SetManagedWindow(self)

        try:
            self.installDir = os.path.dirname(os.path.abspath(__file__))
        except:
            self.installDir = os.path.dirname(os.path.abspath(sys.argv[0]))

        # We run at 5/6 of that size
        xvideo = wx.SystemSettings_GetMetric(wx.SYS_SCREEN_X)
        yvideo = wx.SystemSettings_GetMetric(wx.SYS_SCREEN_Y)

        self.SetSize((3*xvideo/4, 4*yvideo/5))

        self.inputPanel = InputPanel(self)
        self.mainPanel = MainPanel(self)

        self.messageWindow = MessageWindow(self)
        self.dbPanelTree = DBPanelTree(self)

        self.dbTree = self.dbPanelTree.dbTree
        
        self.database = Database(self)

        self.legendPanel = LegendPanel(self)
        minSize = self.legendPanel.GetSizer().CalcMin()

        minInput = self.inputPanel.GetMinSize()
        self._mgr.AddPane(self.mainPanel, aui.AuiPaneInfo().CenterPane().Name(u"MainNotebook"))
        self._mgr.AddPane(self.inputPanel, aui.AuiPaneInfo().Top().Caption(u"Input Data / Diff Selection").Name(u"InputPanel"). \
                          FloatingSize(wx.Size(500, 200)).MinimizeButton(True).MinSize(wx.Size(0, minInput.y)))
        
        self._mgr.AddPane(self.dbPanelTree, aui.AuiPaneInfo().Left().Caption(u"Libraries Info").Name(u"DBTree"). \
                          MinSize(wx.Size(120, -1)).BestSize(wx.Size(150, -1)).FloatingSize(wx.Size(150, 400)).
                          Layer(1))

        self._mgr.AddPane(self.messageWindow, aui.AuiPaneInfo().Bottom().
                          Caption(u"Messages & Actions").MinSize(wx.Size(200, 100)).
                          FloatingSize(wx.Size(500, 300)).BestSize(wx.Size(200, yvideo/10)).
                          MaximizeButton().MinimizeButton().Name(u"MessageWindow"))

        self._mgr.AddPane(self.legendPanel, aui.AuiPaneInfo().Name(u"LegendPanel").
                          Caption(u"Diff Legend").MinSize(minSize).
                          FloatingSize(minSize).BestSize(minSize).
                          Dockable(False).Float().Hide().MinimizeButton())

        self.processTimer = wx.Timer(self)   # used to monitor the external process

        self.CreateMenuBar()        
        self.CreateBar()
        self.SetupAccelerators()
        
        self.SetAllFlags()
        self.ReadPreferences()
        self.UpdateActionsMenu()

        self.SetProperties()
        self.BuildNBImageList()

        self.BindEvents()

        wx.CallLater(500, self.LoadDatabase)
        

    def SetProperties(self):

        icon = wx.IconFromBitmap(catalog["NDT"].GetBitmap())
        self.SetIcon(icon)
        self.SetTitle(u"Namespace Diff Tool v%s"%__version__)


    def BuildNBImageList(self):
        """ Builds a fake image list for aui.AuiNotebook. """

        # One day someone will explain why it doesn't handle wx.ImageList
        # like every other Book.
        self.nbTopImageList = []
        for png in _globalIcons:
            bmp = self.CreateBitmap(png)
            self.nbTopImageList.append(bmp)


    def ReadPreferences(self):

        app = wx.GetApp()
        
        self.showInherited = app.GetPreference("SHOW_INHERITED", default=False)
        self.greyInherited = app.GetPreference("GREY_INHERITED", default=True)        
        self.highlightDocs = app.GetPreference("HIGHLIGHT_DOCS", default=False)
        self.pythonVersion = app.GetPreference("PYTHON_VERSION", default=sys.executable)
        self.diffLegend = app.GetPreference("SHOW_LEGEND", default=True)
        self.treeTips = app.GetPreference("TREE_TOOLTIPS", default=False)
        
        self.actions = app.GetPreference("NDT_ACTIONS", default=[])
        
        frame_pos = app.GetPreference("FRAME_POSITION", default=None)
        frame_size = app.GetPreference("FRAME_SIZE", default=None)
        maximize = app.GetPreference("FRAME_MAXIMIZE", default=False)

        history = app.GetPreference("FILE_HISTORY", default=[])

        for his in history:
            self.filehistory.AddFileToHistory(his)
                    
        notebook_style = app.GetPreference("NOTEBOOK_STYLE", default=self.notebook_style)
        
        notebook_art = app.GetPreference("NOTEBOOK_ART", default=0)
        dock_art = app.GetPreference("DOCK_ART", default=1)
        perspective = app.GetPreference("PERSPECTIVE", default="")

        if dock_art == 0:
            try:
                self._mgr.SetArtProvider(aui.ModernDockArt(self))
            except (NameError, AttributeError):
                self._mgr.SetArtProvider(aui.AuiDefaultDockArt())
                dock_art = 1
        else:
            self._mgr.SetArtProvider(aui.AuiDefaultDockArt())

        self.mainPanel.SetAGWWindowStyleFlag(notebook_style)
        
        if notebook_art == 0:
            self.mainPanel.SetArtProvider(aui.AuiDefaultTabArt())

        elif notebook_art == 1:
            self.mainPanel.SetArtProvider(aui.FF2TabArt())
        
        elif notebook_art == 2:
            self.mainPanel.SetArtProvider(aui.VC71TabArt())

        elif notebook_art == 3:
            self.mainPanel.SetArtProvider(aui.VC8TabArt())

        elif notebook_art == 4:
            self.mainPanel.SetArtProvider(aui.ChromeTabArt())

        self.notebook_art = notebook_art
        self.dock_art = dock_art
        
        self.notebook_style = notebook_style

        if frame_size and frame_size[0] > 300 and frame_size[1] > 300:
            self.SetSize(frame_size)

        if frame_pos and frame_pos[0] > -10 and frame_pos[1] > -10:
            self.SetPosition(frame_pos)
        else:
            self.CenterOnScreen()

        if perspective:
            self._mgr.LoadPerspective(perspective)

        self._mgr.GetPane(self.legendPanel).Hide()

        self.mainPanel._mgr.SetArtProvider(NDTDockArt(self))
        
        self._mgr.Update()

        if maximize:
            self.Maximize()

        self.CleanUp()
        
        welcome_string = u"Namespace Diff Tool version %s successfully started at %s"%(__version__, now())        
        self.SendMessage(u"Message", welcome_string)


    def CreateBar(self):

        self.statusBar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
        self.statusBar.SetStatusWidths([-1, -2])

        # Get the wxPython version information
        wxPythonVersion = wx.version()
        major, minor, micro, release = sys.version_info[0:-1]
        pythonVersion = u"%d.%d.%d-%s"%(major, minor, micro, release)
        statusText = u"Python %s, wxPython %s"%(pythonVersion, wxPythonVersion)

        # Ah, by the way, thank you menu bar for deleting my status bar messages...            
        self.statusBar.SetStatusText(statusText, 1)
        self.statusBar.SetStatusText(u"Welcome to Namespace Diff Tool", 0)


    def CreateMenuBar(self):

        fileMenu  = wx.Menu()
        optionsMenu = wx.Menu()
        viewMenu = wx.Menu()
        interfaceMenu = wx.Menu()
        dockingMenu = wx.Menu()
        nbMenu = wx.Menu()

        helpMenu = wx.Menu()
        recentMenu = wx.Menu()
        actionsMenu = wx.Menu()

        # Add sub-menu to main menu
        item = wx.MenuItem(fileMenu, wx.ID_ANY, "Recent &Libraries", "Sets the selected library as current", wx.ITEM_NORMAL, recentMenu)
        item.SetBitmap(self.CreateBitmap("recent"))
        fileMenu.AppendItem(item)

        self.historyMenu = recentMenu

        self.filehistory = wx.FileHistory()
        self.filehistory.UseMenu(self.historyMenu)

        item = wx.MenuItem(fileMenu, wx.ID_ANY, "Recent &Actions", "Reloads an NDT configuration and executes it", wx.ITEM_NORMAL, actionsMenu)
        item.SetBitmap(self.CreateBitmap("action"))
        fileMenu.AppendItem(item)

        self.actionsMenu = actionsMenu        
        
        fileMenu.AppendSeparator()
        
        item = wx.MenuItem(fileMenu, wx.ID_EXIT, "E&xit\tCtrl+X", "Exit NDT", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("exit"))
        self.Bind(wx.EVT_MENU, self.OnClose, id=item.GetId())
        fileMenu.AppendItem(item)

        item = wx.MenuItem(optionsMenu, wx.ID_ANY, "Change &Python version...\tCtrl+P", "Allows to modify the Python version used to import libraries")
        item.SetBitmap(self.CreateBitmap("python"))
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnChangePython, id=item.GetId())

        optionsMenu.AppendSeparator()

        item = wx.MenuItem(optionsMenu, ID_ShowLegend, "Show &legend in diff mode", "Shows/hides the helper legend in diff mode", wx.ITEM_CHECK)
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnOptions, id=item.GetId())

        item = wx.MenuItem(optionsMenu, ID_IgnoreList, "Set &ignore list for diffs...\tCtrl+I", "Sets a list of ignored differences in diff mode", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("ignore"))
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnIgnore, id=item.GetId())

        optionsMenu.AppendSeparator()
        
        item = wx.MenuItem(optionsMenu, ID_ShowInherited, "Show '&inherited' items", "Shows/hides the inherited items in the tree", wx.ITEM_CHECK)
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnOptions, id=item.GetId())

        item = wx.MenuItem(optionsMenu, ID_GreyInherited, "&Grey out 'inherited' items", "Greys out inherited items in the tree", wx.ITEM_CHECK)
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnOptions, id=item.GetId())

        item = wx.MenuItem(optionsMenu, ID_HighlightDocs, "&Highlight items with no docstrings", "Shows an icon on the left for items without docstrings", wx.ITEM_CHECK)
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnOptions, id=item.GetId())

        optionsMenu.AppendSeparator()

        item = wx.MenuItem(optionsMenu, ID_TreeTips, "Show &tooltips on tree items", "Shows/hides the tooltips while hovering on a tree item", wx.ITEM_CHECK)
        optionsMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnOptions, id=item.GetId())
 
        optionsMenu.AppendSeparator()

        item = wx.MenuItem(optionsMenu, wx.ID_ANY, "&Vacuum database\tCtrl+V", "Cleans up and frees space in the database", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("vacuum"))
        optionsMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnVacuum, id=item.GetId())

        item = wx.MenuItem(viewMenu, wx.ID_ANY, "&Libraries info...\tCtrl+L", "Shows information about the libraries in the database", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("info"))
        viewMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnLibrariesInfo, id=item.GetId())

        item = wx.MenuItem(viewMenu, wx.ID_ANY, "Open &database location\tCtrl+D", "Opens the folder where the NDT database lives", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("explore"))
        viewMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnExplore, id=item.GetId())
        
        item = wx.MenuItem(interfaceMenu, wx.ID_ANY, "Save panels &configuration...", "Saves the current GUI appearance", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("newconfig"))
        interfaceMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnSaveConfig, id=item.GetId())
        
        item = wx.MenuItem(interfaceMenu, ID_FirstPerspective, "Restore original &GUI\tCtrl+G", "Restores the original GUI appearance", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("restoreconfig"))
        interfaceMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnRestoreConfig, id=item.GetId())

        interfaceMenu.AppendSeparator()
        
        # Add sub-menu to main menu
        item = wx.MenuItem(interfaceMenu, wx.ID_ANY, "&Docking style", "Changes the docking style appearance", wx.ITEM_NORMAL, dockingMenu)
        item.SetBitmap(self.CreateBitmap("docking"))
        interfaceMenu.AppendItem(item)

        item = wx.MenuItem(dockingMenu, ID_Modern, "&Modern style", "Modern docking style", wx.ITEM_RADIO)
        dockingMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnDocking, id=ID_Modern)
        
        item = wx.MenuItem(dockingMenu, ID_Classic, "&Classic style", "Classic docking style", wx.ITEM_RADIO)
        dockingMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnDocking, id=ID_Classic)

        # Add sub-menu to main menu
        bmp = self.CreateBitmap("notebook")
        item = wx.MenuItem(interfaceMenu, wx.ID_ANY, "&Notebook style", "Changes the notebook style appearance", wx.ITEM_NORMAL, nbMenu)
        item.SetBitmap(self.CreateBitmap("notebook"))
        interfaceMenu.AppendItem(item)

        item = wx.MenuItem(nbMenu, ID_Top, "Tabs at &top", "Notebook tabs are shown at the top", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_Top)

        item = wx.MenuItem(nbMenu, ID_Bottom, "Tabs at &bottom", "Notebook tabs are shown at the bottom", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)        
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_Bottom)

        nbMenu.AppendSeparator()
        
        item = wx.MenuItem(nbMenu, ID_NB_Default, "&Default tab style", "Default style for notebook tabs", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_NB_Default)

        item = wx.MenuItem(nbMenu, ID_NB_FF2, "&Firefox 2 tab style", "Firefox 2 style for notebook tabs", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_NB_FF2)
        
        item = wx.MenuItem(nbMenu, ID_NB_VC71, "&VC71 tab style", "Visual Studio 2003 style for notebook tabs", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_NB_VC71)

        item = wx.MenuItem(nbMenu, ID_NB_VC8, "VC&8 tab style", "Visual Studio 2005 style for notebook tabs", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_NB_VC8)

        item = wx.MenuItem(nbMenu, ID_NB_Chrome, "&Chrome tab style", "Google Chrome style for notebook tabs", wx.ITEM_RADIO)
        nbMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnNotebook, id=ID_NB_Chrome)

        interfaceMenu.AppendSeparator()
        
        item = wx.MenuItem(interfaceMenu, ID_InputPane, "&Input pane", "Shows/hides the input pane", wx.ITEM_CHECK)
        interfaceMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnView, id=ID_InputPane)

        item = wx.MenuItem(interfaceMenu, ID_MessageWindow, "&Message window", "Shows/hides the message window", wx.ITEM_CHECK)
        interfaceMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnView, id=ID_MessageWindow)

        item = wx.MenuItem(interfaceMenu, ID_DBTree, "&Libraries tree", "Shows/hides the libraries tree", wx.ITEM_CHECK)
        interfaceMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnView, id=ID_DBTree)

        item = wx.MenuItem(helpMenu, wx.ID_ABOUT, "&About...", "About NDT", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("about"))
        helpMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnAbout, id=item.GetId())

        item = wx.MenuItem(helpMenu, wx.ID_ANY, "NDT &version info...", "Shows some information about NDT", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("versioninfo"))
        helpMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnVersionInfo, id=item.GetId())

        helpMenu.AppendSeparator()

        item = wx.MenuItem(helpMenu, wx.ID_ANY, "&Contact the author...", "Send an e-mail to Andrea Gavana", wx.ITEM_NORMAL)
        item.SetBitmap(self.CreateBitmap("contact"))
        helpMenu.AppendItem(item)
        self.Bind(wx.EVT_MENU, self.OnContact, id=item.GetId())

        self._mb = wx.MenuBar()
        
        # Add menu to the menu bar
        self._mb.Append(fileMenu, "&File")
        self._mb.Append(optionsMenu, "&Options")
        self._mb.Append(viewMenu, "&View")
        self._mb.Append(interfaceMenu, "&Interface")
        self._mb.Append(helpMenu, "&Help")

        self.SetMenuBar(self._mb)        

        self.config_menu = interfaceMenu


    def SetAllFlags(self):

        flags = self._mgr.GetAGWFlags()
        flags += aui.AUI_MGR_ALLOW_ACTIVE_PANE | aui.AUI_MGR_TRANSPARENT_DRAG | aui.AUI_MGR_AERO_DOCKING_GUIDES | \
                 aui.AUI_MGR_SMOOTH_DOCKING

        if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
             flags += aui.AUI_MGR_LIVE_RESIZE
        
        self._mgr.SetAGWFlags(flags)
        self.mainPanel.SetUniformBitmapSize((16, 16))

        all_panes = self._mgr.GetAllPanes()
        minize_mode = aui.AUI_MINIMIZE_POS_SMART
        for pane in all_panes:
            pane.MinimizeMode(minize_mode | (pane.GetMinimizeMode() & aui.AUI_MINIMIZE_CAPT_MASK))

        minize_mode = aui.AUI_MINIMIZE_CAPT_SMART
        for pane in all_panes:
            pane.MinimizeMode(minize_mode | (pane.GetMinimizeMode() & aui.AUI_MINIMIZE_POS_MASK))

        self.notebook_style = aui.AUI_NB_DEFAULT_STYLE|aui.AUI_NB_WINDOWLIST_BUTTON| \
                              aui.AUI_NB_SMART_TABS|aui.AUI_NB_CLOSE_ON_ALL_TABS| \
                              aui.AUI_NB_NO_TAB_FOCUS|aui.AUI_NB_TAB_FLOAT

        self.mainPanel.SetAGWWindowStyleFlag(self.notebook_style)
        
        self.perspectives = []        
        self.perspectives.append(self._mgr.SavePerspective())


    def BindEvents(self):

        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_QUERY_END_SESSION, self.OnClose)

        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_InputPane)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_MessageWindow)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_DBTree)

        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_Modern)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_Classic)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_Top)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_Bottom)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_NB_Default)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_NB_FF2)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_NB_VC71)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_NB_VC8)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_NB_Chrome)

        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_ShowLegend)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_ShowInherited)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_GreyInherited)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_TreeTips)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_HighlightDocs)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_IgnoreList)
        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_CloseAllSubTabs)
        
        self.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory, id=wx.ID_FILE1, id2=wx.ID_FILE9+1)
        self.Bind(wx.EVT_MENU_RANGE, self.OnActionHistory, id=_actionIds[0], id2=_actionIds[-1])

        self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.OnPageClosed, self.mainPanel)
        self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnPageChanged, self.mainPanel)
        self.Bind(aui.EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabsRight, self.mainPanel)

        self.Bind(wx.EVT_TIMER, self.OnProcessTimer, self.processTimer)
        # We monitor the external compilation process, when started
        self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)        


    def SetupAccelerators(self):

        # Add accelerators for the Ctrl+W and F5 switches
        idClose, idRun = wx.NewId(), wx.NewId()
        self.Bind(wx.EVT_MENU, self.OnPageClosed, id=idClose)
        self.Bind(wx.EVT_MENU, self.OnStart, id=idRun)

        commands = [(wx.ACCEL_CTRL, ord("w"), idClose), (wx.ACCEL_NORMAL, wx.WXK_F5, idRun)]
        self.accelTable = wx.AcceleratorTable(commands)
        
        # Set the accelerator table
        self.SetAcceleratorTable(self.accelTable)


    def UpdateActionsMenu(self):

        for ids in _actionIds:
            item = self.actionsMenu.FindItemById(ids)
            if item:
                self.actionsMenu.DeleteItem(item)
            
        count = 0
        for act, fileName, library1, library2 in self.actions:
            if act == SCAN:
                menuName = "Scan: %s"%fileName
                bmp = "scan"
            elif act == LOAD:
                menuName = "Load: %s"%library1
                bmp = "load"
            else:
                menuName = "Compare: %s vs. %s"%(library1, library2)
                bmp = "comparison"

            # Add sub-menu to main menu
            item = wx.MenuItem(self.actionsMenu, _actionIds[count], menuName, menuName, wx.ITEM_NORMAL)
            item.SetBitmap(self.CreateBitmap(bmp))
            self.actionsMenu.InsertItem(0, item)
            count += 1


    def RemoveActionMenus(self, package_name):

        count = 0
        name = package_name.split("-")[0]
        actions = self.actions[:]
                
        for action, fileName, library1, library2 in actions:
            if name in fileName or name in library1 or name in library2:
                actions.pop(count)
                continue
            count += 1

        self.actions = actions[:]
        self.UpdateActionsMenu()
        
        
    def CreateBitmap(self, png):
        
        return catalog[png].GetBitmap()


    def SendMessage(self, kind, message):
        """ Sends a message to the log window at the bottom. """
        
        self.messageWindow.SendMessage(kind, message)


    def LoadDatabase(self):

        busy = PBI.PyBusyInfo('Loading database information...', parent=self, title="NDT is busy",
                              icon=self.CreateBitmap("NDT"))
        wx.SafeYield()

        self.database.Load(wx.GetApp().GetDatabase())
        del busy


    def AddPackage(self, package_class):

        busy = PBI.PyBusyInfo("Saving library %s to database..."%package_class.name, parent=self, title="NDT is busy",
                              icon=self.CreateBitmap("NDT"))
        wx.SafeYield()

        self.database.AddPackage(package_class)
        self.CleanUp()
            
        del busy
        

    def CleanUp(self):

        dbDir = os.path.split(wx.GetApp().GetDatabase())[0]
        pickleFile = os.path.join(dbDir, _pklFile)

        if os.path.isfile(pickleFile):
            try:
                os.remove(pickleFile)
            except OSError:
                return
        

    def AddFileToHistory(self, path):

        self.filehistory.AddFileToHistory(path)
        

    def FindPage(self, klass1, klass2=None):

        pageName = GetTopLevelParent(klass1).name
        
        if klass2 is not None:
            pageName += " vs. %s"%GetTopLevelParent(klass2).name

        for indx in xrange(self.mainPanel.GetPageCount()):
            text = self.mainPanel.GetPageText(indx)
            if text == pageName:
                return indx, pageName

        return -1, pageName  
        

    def NewPage(self, package_class, other_package_class=None):

        self.Freeze()

        mainPage, pageName = self.FindPage(package_class, other_package_class)
        
        if mainPage >= 0:
            self.mainPanel.DeletePage(mainPage)
            
        mainPage = NotebookPage(self.mainPanel, package_class, other_package_class)
        mainPage.mode = NDTMode(package_class, other_package_class, u'', None, [])

        if other_package_class is None:
            
            library = package_class.name.lower()
            bmp = self.CreateBitmap("python")
            self.nbTopImageList[0] = bmp
            
            for pkgs in _knownPackages:
                if pkgs in library:
                    bmp = self.CreateBitmap(pkgs)
                    self.nbTopImageList[0] = bmp
                    break
                
            image = self.nbTopImageList[package_class.GetImage()]

        else:

            image = self.CreateBitmap("comparison")            

        self.mainPanel.AddPage(mainPage, pageName, True, image)

        self.Thaw()


    def NewSubPage(self, package_class, other_package_class, treeItem):

        self.Freeze()
        mainPage, pageName = self.FindPage(package_class, other_package_class)
        self.mainPanel.GetPage(mainPage).NewPage(treeItem)
        self.Thaw()
        

    def OnChangePython(self, event):
        """ Changes the Python version. """

        default = os.path.split(self.pythonVersion)[0]
        
        if wx.Platform == "__WXMSW__":
            wildcard = "Python executable (*.exe)|*.exe"
            fileName = "python.exe"
        else:
            wildcard = "All files (*.*)|*.*"
            fileName = "python"
            
        dlg = wx.FileDialog(self, message="Please select the new Python executable...", defaultDir=default,
                            defaultFile=fileName, wildcard=wildcard,
                            style=wx.FD_OPEN|wx.FD_CHANGE_DIR)

        dlg.CenterOnParent()
        
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            dlg.Destroy()
            # Let's see if it is a real python.exe file...
            directory, fileName = os.path.split(path)
            if wx.Platform == "__WXMSW__":
                if fileName.lower() != "python.exe":
                    self.RunError("Error", "The selected file is not a Python executable.")
                    return

            self.SendMessage("Message", "Python executable changed from %s to %s"%(self.pythonVersion, path))
            self.pythonVersion = path
            
        else:
            # Destroy the dialog. Don't do this until you are done with it!
            # BAD things can happen otherwise!
            dlg.Destroy()        


    def OnOptions(self, event):

        evId = event.GetId()
        checked = event.IsChecked()
        
        if evId == ID_TreeTips:
            self.treeTips = checked
            return
        elif evId == ID_ShowLegend:
            self.diffLegend = checked
            self._mgr.GetPane(self.legendPanel).Show(self.diffLegend)
            self._mgr.Update()
            return
        elif evId == ID_ShowInherited:
            self.showInherited = checked
        elif evId == ID_GreyInherited:
            self.greyInherited = checked
        elif evId == ID_HighlightDocs:
            self.highlightDocs = checked
            
        if self.mainPanel.GetPageCount() > 0:
            page = self.mainPanel.GetCurrentPage()
            self.dbTree.PopulateTree(page.mode)


    def OnIgnore(self, event):

        page = self.mainPanel.GetCurrentPage()
        dlg = IgnoreDialog(self, page.mode)

        if dlg.ShowModal() == wx.ID_OK:
            new = dlg.GetIgnoreList()
            if new != page.mode.ignoreList:
                page.mode.ignoreList = new[:]
                self.dbTree.PopulateTree(page.mode)

        dlg.Destroy()                

        
    def OnVacuum(self, event):

        busy = PBI.PyBusyInfo("Cleaning up and freeing space in the NDT database...", parent=self, title="NDT is busy",
                              icon=self.CreateBitmap("NDT"))
        wx.SafeYield()

        self.database.Vacuum()

        del busy
        

    def OnLibrariesInfo(self, event):

        busy = PBI.PyBusyInfo("Loading data from the NDT database...", parent=self, title="NDT is busy",
                              icon=self.CreateBitmap("NDT"))
        wx.SafeYield()

        allDB, dbInfo = self.database.GetAllPackages()
        frame = LibraryFrame(self, allDB, dbInfo)

        del busy


    def OnExplore(self, event):

        dbDir = os.path.split(wx.GetApp().GetDatabase())[0]

        platform = wx.Platform
        
        if platform == "__WXMSW__":
            os.startfile(dbDir)
            return
        
        if platform == "__WXGTK__":
            cmd = ['xdg-open', dbDir]
        else:
            # Mac
            cmd = ['open', dbDir]

        subprocess.Popen(cmd, shell=(not subprocess.mswindows))     
            

    def OnRestoreConfig(self, event):

        self._mgr.LoadPerspective(self.perspectives[0])
        self._mgr.Update()
        
        event.Skip()
    

    def OnRestorePerspective(self, event):

        self._mgr.LoadPerspective(self.perspectives[event.GetId() - ID_FirstPerspective])
        self._mgr.Update()
        

    def OnSaveConfig(self, event):

        dlg = wx.TextEntryDialog(self, "Enter a name for the new configuration:", "NDT action: saving UI appearance")
        
        dlg.SetValue(("Perspective %d")%(len(self.perspectives)))
        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return
        
        if len(self.perspectives) == 1:
            self.config_menu.AppendSeparator()

        item_sim = wx.MenuItem(self.config_menu, ID_FirstPerspective + len(self.perspectives), dlg.GetValue(),
                               "Loads UI settings %s"%dlg.GetValue(), wx.ITEM_NORMAL, None)
        item_sim.SetBitmap(self.CreateBitmap("loadconfig"))
        self.config_menu.AppendItem(item_sim)
        
        self.Bind(wx.EVT_MENU, self.OnRestorePerspective, id=item_sim.GetId())        
        self.perspectives.append(self._mgr.SavePerspective())

        dlg.Destroy()
        event.Skip()


    def OnDocking(self, event):

        evId = event.GetId()
        if evId == ID_Classic:
            self._mgr.SetArtProvider(aui.AuiDefaultDockArt())
            self.dock_art = 1
        elif evId == ID_Modern:
            try:
                self._mgr.SetArtProvider(aui.ModernDockArt(self))
                self.dock_art = 0
            except (NameError, AttributeError):
                pass

        self._mgr.Update()
        self.Refresh()


    def OnNotebook(self, event):

        evId = event.GetId()

        style = self.mainPanel.GetAGWWindowStyleFlag()
        tabart = None

        if evId == ID_NB_Default:
            tabart = aui.AuiDefaultTabArt()
            self.notebook_art = 0
        
        elif evId == ID_NB_VC71:
            tabart = aui.VC71TabArt()
            self.notebook_art = 1

        elif evId == ID_NB_FF2:
            tabart = aui.FF2TabArt()
            self.notebook_art = 2

        elif evId == ID_NB_VC8:
            tabart = aui.VC8TabArt()
            self.notebook_art = 3

        elif evId == ID_NB_Chrome:
            tabart = aui.ChromeTabArt()
            self.notebook_art = 4

        elif evId == ID_Top:
            style &= ~aui.AUI_NB_BOTTOM
            style ^= aui.AUI_NB_TOP

            self.notebook_style = style
            self.mainPanel.SetAGWWindowStyleFlag(self.notebook_style)

        elif evId == ID_Bottom:
            style &= ~aui.AUI_NB_TOP
            style ^= aui.AUI_NB_BOTTOM

            self.notebook_style = style
            self.mainPanel.SetAGWWindowStyleFlag(self.notebook_style)

        if evId in [ID_Bottom, ID_Top]:
            for child in self.mainPanel.GetChildren():
                if isinstance(child, aui.AuiNotebook):
                    child.SetAGWWindowStyleFlag(self.notebook_style)

        if tabart:
            self.mainPanel.SetArtProvider(tabart)
            for child in self.mainPanel.GetChildren():
                if isinstance(child, aui.AuiNotebook):
                    child.SetArtProvider(tabart)
            
        self.mainPanel.Refresh()
        self.mainPanel.Update()


    def OnView(self, event):

        evId = event.GetId()
        
        if evId == ID_InputPane:
            pane = self._mgr.GetPane("InputPanel")
        elif evId == ID_MessageWindow:
            pane = self._mgr.GetPane("MessageWindow")
        elif evId == ID_DBTree:
            pane = self._mgr.GetPane("DBTree")

        pane.Show(not pane.IsShown())
        self._mgr.Update()
        

    def OnAbout(self, event):

        msg = "This is the about dialog of the Namespace Diff Tool (NDT).\n\n" + \
              "Version: " + __version__ + "\n" + \
              "Author: Andrea Gavana @ 05 October 2011\n\n" + \
              "Please report any bug/requests of improvements to me at the\n" \
              "following addresses:\n\n" + \
              "andrea.gavana@gmail.com\n" + "andrea.gavana@maerskoil.com\n"

        self.RunError("Message", msg, "About NDT...")


    def OnVersionInfo(self, event):

        dlg = VersionInfo(self, __locs__)
        dlg.ShowModal()
        dlg.Destroy()
        wx.SafeYield()
        

    def OnContact(self, event):

        wx.BeginBusyCursor()
        webbrowser.open("mailto:andrea.gavana@gmail.com?subject=Comments on NDT&cc=andrea.gavana@maerskoil.com")
        wx.CallAfter(wx.EndBusyCursor)


    def OnUpdateUI(self, event):
    
        evId = event.GetId()
        
        if evId == ID_Modern:
            event.Enable(self._mgr.CanUseModernDockArt())
            event.Check(self.dock_art == 0)
        elif evId == ID_Classic:
            event.Check(self.dock_art == 1)
        elif evId == ID_Top:
            event.Check(self.notebook_style & aui.AUI_NB_TOP)
        elif evId == ID_Bottom:
            event.Check(self.notebook_style & aui.AUI_NB_BOTTOM)
        elif evId == ID_NB_Default:
            event.Check(self.notebook_art == 0)
        elif evId == ID_NB_FF2:
            event.Check(self.notebook_art == 1)
        elif evId == ID_NB_VC71:
            event.Check(self.notebook_art == 2)
        elif evId == ID_NB_VC8:
            event.Check(self.notebook_art == 3)
        elif evId == ID_NB_Chrome:
            event.Check(self.notebook_art == 4)
        elif evId == ID_MessageWindow:
            pane = self._mgr.GetPane("MessageWindow")
            event.Check(pane.IsShown())
        elif evId == ID_InputPane:
            pane = self._mgr.GetPane("InputPanel")
            event.Check(pane.IsShown())
        elif evId == ID_DBTree:
            pane = self._mgr.GetPane("DBTree")
            event.Check(pane.IsShown())
        elif evId == ID_TreeTips:
            event.Check(self.treeTips)
        elif evId == ID_ShowLegend:
            event.Check(self.diffLegend)
        elif evId == ID_ShowInherited:
            event.Check(self.showInherited)
        elif evId == ID_GreyInherited:
            event.Check(self.greyInherited)
        elif evId == ID_HighlightDocs:
            event.Check(self.highlightDocs)
        elif evId == ID_IgnoreList:
            current = self.mainPanel.GetCurrentPage()
            enable = False
            if current is not None:
                mode = current.mode
                if mode.other_package_class is not None:
                    enable = True
            event.Enable(enable)
        elif evId == ID_CloseAllSubTabs:
            page = self.mainPanel.GetCurrentPage()
            event.Enable(page is not None and page.GetPageCount() > 0)

            
    def OnFileHistory(self, event):
        
        # get the file based on the menu ID
        fileNum = event.GetId() - wx.ID_FILE1
        fileName = self.filehistory.GetHistoryFile(fileNum)

        # add it back to the history so it will be moved up the list
        self.filehistory.AddFileToHistory(fileName)

        self.inputPanel.SetInput(fileName)


    def OnActionHistory(self, event):

        base = 11000
        evId = event.GetId()

        item = evId - base
        action, fileName, library1, library2 = self.actions[item]

        if action == SCAN:
            self.inputPanel.SetInput(fileName)
        elif action == LOAD:
            self.inputPanel.SetInput(library1, isFile=False)
        else:
            self.inputPanel.SetInput(library1, library2)

        self.OnStart()
        

    def OnStart(self, event=None):

        if not self.inputPanel.Validate():
            return
        
        kind = self.inputPanel.GetProcess()
        fileName = library1 = library2 = ''
        
        if kind == SCAN:
            init_name, import_name, version = self.inputPanel.GetPackageInfo()
            self.SendMessage("Message", "Starting the dynamic import process for library '%s'..."%import_name)
            self.process = ImportProcess(self, init_name, import_name, version)
            self.inputPanel.EnableButtons(False)

            # Start the monitoring timer
            self.processTimer.Start(1000)            
            # Start the process
            self.process.Start()

            fileName = init_name            

        elif kind == LOAD:
            wx.BeginBusyCursor()
            package_name = self.inputPanel.GetLoadingInfo()
            self.SendMessage("Message", "Loading library '%s' from database..."%package_name)
            package_class = self.database.LoadPackage(package_name)
            self.NewPage(package_class)
            self.SendMessage("Message", "Library '%s' successfully imported"%package_name)
            wx.EndBusyCursor()

            library1 = package_name            

        elif kind == COMPARE:
            wx.BeginBusyCursor()
            name1, name2 = self.inputPanel.GetComparisonInfo()
            self.SendMessage("Message", "Loading libraries '%s' and '%s' from database..."%(name1, name2))
            package_class = self.database.LoadPackage(name1)
            other_package_class = self.database.LoadPackage(name2)
            self.SendMessage("Message", "Libraries '%s' and '%s' successfully imported"%(name1, name2))
            self.SendMessage("Message", "Populating library tree...")
            self.NewPage(package_class, other_package_class)
            self.SendMessage("Message", "Done")
            wx.EndBusyCursor()

            library1, library2 = name1, name2
        
        tup = (kind, fileName, library1, library2)
        if tup in self.actions:
            self.actions.remove(tup)
        else:
            lenAct = len(self.actions)
            if lenAct > 9:
                self.actions.pop(lenAct-1)
            
        self.actions.append(tup)
        self.UpdateActionsMenu()
            

    def OnKill(self, event=None):

        answer = self.RunError("Question", "Are you sure you wish to stop the NDT scan process?")
        
        if answer != wx.ID_YES:
            return

        # Stop the process timer
        self.processTimer.Stop()
        # This doesn't work, at least on Windows.
        # The process is still there and there is no os.kill on Windows
        self.process.Kill()
        
        self.Stop()
        self.SendMessage("Warning", "NDT scan process killed by the user")


    def Stop(self):

        self.inputPanel.EnableButtons(True)


    def OnPageClosed(self, event):

        event.Skip()

        if self.mainPanel.GetPageCount() == 0:
            self.dbTree.DeleteAllItems()


    def OnPageChanged(self, event):

        oldSelection = event.GetOldSelection()
        newSelection = event.GetSelection()
        
        if oldSelection >= 0:
            page = self.mainPanel.GetPage(oldSelection)
            page.mode = self.dbTree.GetMode()
        if newSelection >= 0:
            page = self.mainPanel.GetPage(newSelection)
            self.dbTree.PopulateTree(page.mode)

        pane = self._mgr.GetPane(self.legendPanel)
        
        if page.mode.other_package_class is not None:            
            if pane.IsShown() != self.diffLegend:
                pane.Show(self.diffLegend)
        else:
            pane.Show(False)

        self._mgr.Update()


    def OnTabsRight(self, event):

        if not hasattr(self, "popupMenu"):
            self.CreatePopupMenu()

        self.currentTab = event.GetSelection()
        self.mainPanel.PopupMenu(self.popupMenu)


    def CreatePopupMenu(self):

        popupMenu = wx.Menu()

        # Create the menu items
        counter = 0
        for icon, ids, text in _popupMenuItems:
            bmp = self.CreateBitmap(icon)
            menuItem = wx.MenuItem(popupMenu, ids, text, "", wx.ITEM_NORMAL)
            menuItem.SetBitmap(bmp)
            popupMenu.AppendItem(menuItem)
            self.Bind(wx.EVT_MENU, self.OnTabsHandling, id=menuItem.GetId())
            
            counter += 1

            if counter == 2:
                popupMenu.AppendSeparator()

        self.popupMenu = popupMenu
        

    def OnTabsHandling(self, event):

        evId = event.GetId()

        self.Freeze()
        
        if evId == ID_CloseTab:
            self.mainPanel.DeletePage(self.mainPanel.GetSelection())
        elif evId == ID_CloseAllTabs:
            for indx in xrange(self.mainPanel.GetPageCount()-1, -1, -1):
                self.mainPanel.DeletePage(indx)

            self.dbTree.DeleteAllItems()                
        else:
            wx.BeginBusyCursor()
            page = self.mainPanel.GetCurrentPage()
            page.Freeze()
            page.CloseAllTabs()
            page.Thaw()
            wx.EndBusyCursor()

        self.Thaw()            


    def OnProcessTimer(self, event):
        """ Handles the wx.EVT_TIMER event for the main frame. """

        # This event runs only when a compilation process is alive
        # When the process finishes (or dies), we simply stop the timer
        if self.process is not None:
            # Defer to the Process class the message scanning
            self.process.HandleProcessMessages()


    def OnProcessEnded(self, event):
        """ Handles the wx.EVT_END_PROCESS for the main frame. """

        # We stop the timer that look for the compilation steps
        self.processTimer.Stop()

        # Handle the (eventual) remaning process messages
        self.process.HandleProcessMessages(True)
        # Destroy the process
        wx.CallAfter(self.process.Destroy)

        # Re-enable the buttons at the bottom
        self.Stop()
        self.process = None
        
        
    def RunError(self, kind, msg, title="", parent=None):

        retval = 0
        if kind == "Error":
            style = wx.OK | wx.ICON_ERROR
        elif kind in ["Warning", "Question"]:
            style = wx.YES_NO | wx.NO_DEFAULT | wx.CANCEL | wx.ICON_QUESTION
            retval = 1
        else:
            style = wx.OK | wx.ICON_INFORMATION

        title = (title and [title] or ['NDT Action: %s'%kind])[0]
        msg = kind + ": %s"%msg

        if parent is None:
            parent = self
            
        dlg = GMD.GenericMessageDialog(parent, msg, title, style)
        answer = dlg.ShowModal()
        dlg.Destroy()
        
        if retval:
            return answer
        
        self.Raise()

        
    def OnClose(self, event):

        if self.process is not None:
            # Try to kill the process. Doesn't work very well
            self.process.Kill()
            self.processTimer.Stop()

        self.CleanUp()
        
        app = wx.GetApp()

        app.SetPreference("SHOW_INHERITED", self.showInherited)
        app.SetPreference("GREY_INHERITED", self.greyInherited)
        app.SetPreference("HIGHLIGHT_DOCS", self.highlightDocs)
        app.SetPreference("PYTHON_VERSION", self.pythonVersion)
        app.SetPreference("SHOW_LEGEND", self.diffLegend)        
        app.SetPreference("TREE_TOOLTIPS", self.treeTips)

        app.SetPreference("NDT_ACTIONS", self.actions)
        
        pos = self.GetPosition()
        size = self.GetSize()
        if pos.x > -10 and pos.y > -10:
            app.SetPreference("FRAME_POSITION", self.GetPosition())

        if size.x > 300 and size.y > 300:            
            app.SetPreference("FRAME_SIZE", self.GetSize())

        app.SetPreference("FRAME_MAXIMIZE", self.IsMaximized())                

        history = []
        for index in xrange(self.filehistory.GetCount()):
            history.append(self.filehistory.GetHistoryFile(index))
            
        app.SetPreference("FILE_HISTORY", history)

        app.SetPreference("NOTEBOOK_STYLE", self.notebook_style)        
        app.SetPreference("NOTEBOOK_ART", self.notebook_art)
        app.SetPreference("DOCK_ART", self.dock_art)
        app.SetPreference("PERSPECTIVE", self._mgr.SavePerspective())

        self.database.CloseSession()
        
        wx.CallAfter(self.Destroy)


    def RaiseException(self, typ, excn, tback):

        sys.excepthook(typ, excn, tback)
        self.isRunning = False

        self.inputPanel.EnableButtons(True)


class MainPanel(aui.AuiNotebook):
    """ 
    Just a simple derived L{aui.AuiNotebook} where we override L{Freeze}
    and L{Thaw} to work  around an issue on wxGTK (Ticket #13543).
    """ 

    def __init__(self, parent):

        aui.AuiNotebook.__init__(self, parent)


    def Freeze(self): 

        if not 'wxGTK' in wx.PlatformInfo:
            return super(MainPanel, self).Freeze() 


    def Thaw(self):

        if not 'wxGTK' in wx.PlatformInfo:
            return super(MainPanel, self).Thaw()


class NDTDockArt(aui.AuiDefaultDockArt):
    """
    A simple derivation of aui.AuiDefaultDockArt to paint a nice background
    on empty AuiNotebooks.
    """
    
    def __init__(self, mainFrame):
        """ Overridden from aui.AuiDefaultDockArt. """

        aui.AuiDefaultDockArt.__init__(self)
        
        self.image1 = mainFrame.CreateBitmap("NDT_big_1")
        self.image1.SetMaskColour(wx.WHITE)
        self.image2 = mainFrame.CreateBitmap("NDT_big_2")
        self.image2.SetMaskColour(wx.WHITE)
        

    def DrawBackground(self, dc, window, orient, rect):
        """
        Draws a background.

        :param `dc`: a wx.DC device context;
        :param `window`: an instance of wx.Window;
        :param `orient`: the gradient (if any) orientation;
        :param `rect`: the background rectangle.
        """

        aui.AuiDefaultDockArt.DrawBackground(self, dc, window, orient, rect)
        
        if "AuiNotebook" in window.GetName():

            xpos = (rect.width - self.image1.GetWidth())/2
            ypos = (rect.height - self.image1.GetHeight())/2

            dc.DrawBitmap(self.image1, xpos, ypos, True)
        
            xpos = (rect.width - self.image2.GetWidth()) - 10
            ypos = (rect.height - self.image2.GetHeight())

            dc.DrawBitmap(self.image2, xpos, ypos, True)
        

class NDTApp(wx.App):

    def __init__(self):

        self.config = None
        self.database = None
        
        wx.App.__init__(self, False)

    
    def OnInit(self):

        sys.excepthook = ExceptionHook
            
        self.SetAppName("NDT")
        self.frame = NDT()
        self.SetTopWindow(self.frame)
        self.frame.Show()
        
        return True
                    

    def OnExit(self, event=None, force=False):
        """
        Handle application exit request

        **Parameters:**
        
        * `event`: event that called this handler
        * `force`: unused at present.
        """

        self.Exit()


    def GetDataDir(self):
        """ Returns the option directory for NDT. """
        
        sp = wx.StandardPaths.Get()
        return sp.GetUserDataDir()


    def GetConfig(self):
        """ Returns the configuration for NDT. """

        if self.config:
            return self.config
        
        if not os.path.exists(self.GetDataDir()):
            # Create the data folder, it still doesn't exist
            os.makedirs(self.GetDataDir())

        self.config = wx.FileConfig(localFilename=os.path.join(self.GetDataDir(), "NDT_Options"))
        return self.config


    def GetDatabase(self):
        """ Returns the database location for NDT. """

        if self.database:
            return self.database
        
        if not os.path.exists(self.GetDataDir()):
            # Create the data folder, it still doesn't exist
            os.makedirs(self.GetDataDir())

        self.database = os.path.join(self.GetDataDir(), "NDT_Database.db")
        return self.database


    def GetPreference(self, preferenceKey=None, default=None):
        """
        Returns the user preferences as stored in wx.Config.

        **Parameters:**

        * `preferenceKey`: the preference to load
        """

        config = self.GetConfig()
        val = config.Read(preferenceKey)
        if not val:
            self.SetPreference(preferenceKey, default)
            return default

        try:
            ret_val = eval(val)
        except (SyntaxError, NameError):
            ret_val = val
            
        return ret_val


    def SetPreference(self, preferenceKey, value):
        """
        Saves the user preferences in wx.Config.

        **Parameters:**

        * `newPreferences`: the new preferences to save        
        """

        config = self.GetConfig()
        config.Write(preferenceKey, str(value))
        config.Flush()


    def GetVersion(self):

        return __version__

        
if __name__ == "__main__":
        
    app = NDTApp()
    app.MainLoop()

    

